using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System.Text;

namespace BreadScrapeWorkerRole
{
    public class WorkerRole : RoleEntryPoint
    {
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
        private CloudStorageAccount storageAccount;
        private CloudTable table;

        public override void Run()
        {
            Trace.TraceInformation("BreadScrapeWorkerRole is running");

            try
            {
                this.RunAsync(this.cancellationTokenSource.Token).Wait();
            }
            finally
            {
                this.runCompleteEvent.Set();
            }
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections
            ServicePointManager.DefaultConnectionLimit = 12;

            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

            bool result = base.OnStart();

            Trace.TraceInformation("BreadScrapeWorkerRole has been started");

            // need to make this code execute only once
            storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);

            // Create the table client.
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

            // Create the CloudTable object that represents the "favorites" table.
            table = tableClient.GetTableReference("Content");

            return result;
        }

        public override void OnStop()
        {
            Trace.TraceInformation("BreadScrapeWorkerRole is stopping");

            this.cancellationTokenSource.Cancel();
            this.runCompleteEvent.WaitOne();

            base.OnStop();

            Trace.TraceInformation("BreadScrapeWorkerRole has stopped");
        }

        private async Task RunAsync(CancellationToken cancellationToken)
        {
            // TODO: Replace the following with your own logic.
            while (!cancellationToken.IsCancellationRequested)
            {
                Trace.TraceInformation("Working");

                DateTime currentTime = System.DateTime.Now;
                int dayDelta = 31; // by default trying to get current month's content
                if (currentTime.Day > 25) dayDelta = 60; // try to get next month's content when it's close to end of current month
                DateTime offsetDay = currentTime.AddDays(dayDelta);
                DateTime nextMonth = new DateTime(offsetDay.Year, offsetDay.Month, 1); // such as in March, we'll try to get content all the way to end of March or end of April
                DateTime dayToExamine = currentTime;
                while (dayToExamine < nextMonth)
                {
                    if (!ContentExist(dayToExamine))
                    {
                        GetContent(dayToExamine);
                    }
                    dayToExamine = dayToExamine.AddDays(1);
                }
                await Task.Delay(1000 * 60 * 60); // wait for 1 hour
            }
        }

        private void GetContent(DateTime date)
        {
            ContentEntity content = new ContentEntity();
            content.currentDate = date;
            string dateUrl = Util.GetDateUrl(date);
            string breadUrl = String.Format("http://odb.org/{0}?calendar-redirect=true", dateUrl);
            
            Uri uri = new Uri(breadUrl);
            if (null != uri)
            {
                Uri redirect = HttpService.GetRedirectUri(uri);
                content.BreadUri = redirect.ToString();
                HttpService.Get(redirect,
                    delegate(string panelResults)
                    {
                        try
                        {
                            Util.ExtractContent(ref content, panelResults);

                            // retrieve the bible verse
                            Uri refUri = new Uri(Util.ConstructVerseUrl(content.VerseName));
                            HttpService.Get(refUri,
                                delegate(string refBibleResults)
                                {
                                    try
                                    {
                                        content.VerseContent = Util.ExtractVerseContent(refBibleResults);
                                        if (!String.IsNullOrEmpty(content.BreadContent) && !String.IsNullOrEmpty(content.VerseContent))
                                        {
                                            InsertContent(content);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        Debug.WriteLine(ex.ToString());
                                    }
                                },
                                delegate(string uriString, Exception ex)
                                {
                                    Debug.WriteLine(ex.ToString());
                                });

                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(ex.ToString());
                        }
                    },
                    delegate(string uriString, Exception ex)
                    {
                        Debug.WriteLine(ex.ToString());
                    });
            }
        }

        private void InsertContent(ContentEntity content)
        {
            string partitionkey = GenerateKey(content.currentDate);
            content.PartitionKey = partitionkey;
            content.RowKey = "1";

            try
            {
                // Create the TableOperation that inserts the comment entity.
                TableOperation insertOperation = TableOperation.InsertOrReplace(content);

                // Execute the insert operation.
                TableResult tr = table.Execute(insertOperation);
                Debug.WriteLine(tr);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        private bool ContentExist(DateTime dayToExamine)
        {
            bool exist = false;

            // PartitionKey looks like "2015-01-08"
            string partitionkey = GenerateKey(dayToExamine);
            string filter = String.Format("(PartitionKey eq '{0}')", partitionkey);

            // Create the table query.
            TableQuery<ContentEntity> query = new TableQuery<ContentEntity>().Where(filter);
            try
            {
                IEnumerable<ContentEntity> result = table.ExecuteQuery(query);
                if (result != null && result.Any())
                {
                    exist = true;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            return exist;
        }

        private string GenerateKey(DateTime date)
        {
            StringBuilder key = new StringBuilder(date.Year.ToString(), 20);
            key.Append('-');
            if (date.Month < 10)
                key.Append('0');
            key.Append(date.Month);
            key.Append('-');
            if (date.Day < 10)
                key.Append('0');
            key.Append(date.Day);
            return key.ToString();
        }
    }
}
